package org.server.http.core;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;

/**
 * 交换区数据流
 * 
 * @author Admin
 * 
 */
public class SwapStream implements Closeable {
	// 请求头最长允许字节
	public static final int HEADER_LINE_MAX_SIZE = 1024;
	// 会话套接字对象
	private Socket socket;
	// 缓冲读取流对象
	private BufferedInputStream reader;
	// 缓冲写入流对象
	private BufferedOutputStream writer;

	/**
	 * * 构造
	 * 
	 * @param sock
	 *            会话套接字
	 * @param timeout
	 *            读取超时限制(ms)
	 * @throws IOException
	 */
	public SwapStream(Socket sock, int timeout) throws IOException {
		socket = sock;
		// 表示对于长时间处于空闲状态(连接的两端没有互相传送数据)的Socket,是否要自动把它关闭,true为是
		// 其默认值为false,表示TCP不会监视连接是否有效,不活动的客户端可能会永久存在下去,而不会注意到服务器已经崩溃
		socket.setKeepAlive(true);
		// 表示是否支持发送一个字节的TCP紧急数据,socket.sendUrgentData(data)用于发送一个字节的TCP紧急数据
		// 其默认为false,即接收方收到紧急数据时不作任何处理,直接将其丢弃..若用户希望发送紧急数据,则应设其为true
		// 设为true后,接收方会把收到的紧急数据与普通数据放在同样的队列中
		socket.setOOBInline(true);
		// 该方法用于设置服务类型,以下代码请求高可靠性和最小延迟传输服务(把0x04与0x10进行位或运算)
		// Socket类用4个整数表示服务类型
		// 0x02:低成本(二进制的倒数第二位为1)
		// 0x04:高可靠性(二进制的倒数第三位为1)
		// 0x08:最高吞吐量(二进制的倒数第四位为1)
		// 0x10:最小延迟(二进制的倒数第五位为1)
		socket.setTrafficClass(0x04 | 0x10);
		// 该方法用于设定连接时间,延迟,带宽的相对重要性(该方法的三个参数表示网络传输数据的3项指标)
		// connectionTime--该参数表示用最少时间建立连接
		// latency---------该参数表示最小延迟
		// bandwidth-------该参数表示最高带宽
		// 可以为这些参数赋予任意整数值,这些整数之间的相对大小就决定了相应参数的相对重要性
		// 如这里设置的就是---最高带宽最重要,其次是最小连接时间,最后是最小延迟
		socket.setPerformancePreferences(2, 1, 3);
		// 设置读取超时时间
		socket.setSoTimeout(timeout);
		// 构建读取/输入缓冲流
		reader = new BufferedInputStream(socket.getInputStream());
		writer = new BufferedOutputStream(socket.getOutputStream());
	}

	/**
	 * 写入数据
	 * 
	 * @param data
	 * @throws IOException
	 */
	public void write(byte[] data) throws IOException {
		writer.write(data);
		writer.flush();
	}

	/**
	 * 尝试从位置i读取数据，最多读取y个，将获取的数据字节存入data，返回读取字节的数量
	 * 
	 * @param data
	 *            字节数组
	 * @param x
	 *            位置
	 * @param y
	 *            读取数量
	 * @return 读取字节的个数
	 * @throws IOException
	 */
	public int read(byte[] data, int x, int y) throws IOException {
		return reader.read(data, x, y);
	}

	/**
	 * 尝试尽可能多的读取数据，返回读取字节的数量
	 * 
	 * @param data
	 *            字节数组
	 * @param x
	 *            位置
	 * @param y
	 *            读取数量
	 * @return 读取字节的个数
	 * @throws IOException
	 */
	public int read(byte[] data) throws IOException {
		return reader.read(data);
	}

	/**
	 * 返回数据读取流
	 * 
	 * @return
	 */
	public InputStream inputStream() {
		return reader;
	}

	/**
	 * 返回数据写入流
	 * 
	 * @return
	 */
	public OutputStream outputStream() {
		return writer;
	}

	/**
	 * 使用指定编码读取一行请求头数据
	 * 
	 * @param charset
	 *            字符编码
	 * @return
	 * @throws IOException
	 */
	public String readNextHeader(String charset) throws IOException {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		int r = reader.read();
		while (r != '\r') {
			if (stream.size() > HEADER_LINE_MAX_SIZE) {
				// 读取失败
				return null;
			}
			stream.write(r);
			r = reader.read();
		}
		// 读取"/n"
		reader.read();
		return stream.toString(charset);
	}

	/**
	 * 使用指定编码写入一行数据
	 * 
	 * @param str
	 * @param charset
	 *            字符编码
	 * @throws IOException
	 * @throws UnsupportedEncodingException
	 */
	public void writerLine(String str, String charset) throws UnsupportedEncodingException, IOException {
		// 写入一行数据
		writer.write(str.getBytes(charset));
		// 写入换行
		writer.write("\r\n".getBytes(charset));
		writer.flush();
	}

	/**
	 * 关闭资源
	 */
	@Override
	public void close() throws IOException {
		// TODO Auto-generated method stub
		if (reader != null) {
			reader.close();
		}
		if (writer != null) {
			writer.close();
		}
		if (socket != null) {
			socket.close();
		}
	}
}
